/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */

package org.apache.directory.studio.persistence.core;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.name.DN;
import org.apache.directory.shared.ldap.name.RDN;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.apache.directory.shared.ldap.schema.ObjectClass;
import org.apache.directory.studio.connection.core.DnUtils;
import org.apache.directory.studio.ldapbrowser.core.model.IEntry;
import org.apache.directory.studio.ldapbrowser.core.model.schema.Schema;
import org.apache.directory.studio.ldapbrowser.core.model.schema.SchemaUtils;


/**
 * TODO LdapEntryAnalyzer.
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory
 *         Project</a>
 */
public class LdapEntryAnalyzer
{

    /**
     * Analyze the entry and set all the values to a
     * 
     * @CodeGeneratorContext object
     * 
     * 
     * @param entry
     *            the entry to be analyzed
     * @return CodeGeneratorContext with all required values injected
     */
    public CodeGeneratorContext analyze( IEntry entry )
    {
        CodeGeneratorContext codeGeneratorContext = new CodeGeneratorContext();
        Collection<ObjectClass> ocds = entry.getObjectClassDescriptions();
        Schema schema = entry.getBrowserConnection().getSchema();

        String packageName = computePackageName( entry.getDn() );
        codeGeneratorContext.setPackageName( packageName );

        ObjectClass strucObjClass = SchemaUtils2.getStructuralObjectClass( ocds, schema );
        String className = getClassName( strucObjClass );
        codeGeneratorContext.setClassName( className );

        codeGeneratorContext.setParentDn( DnUtils.getParent( entry.getDn() ).toString() );
        codeGeneratorContext.setRdnAttribute( entry.getRdn().getUpType() );
        codeGeneratorContext.setServer( entry.getBrowserConnection().getConnection().getHost() );
        codeGeneratorContext.setPort( entry.getBrowserConnection().getConnection().getPort() );
        codeGeneratorContext.setBindDn( entry.getBrowserConnection().getConnection().getBindPrincipal() );
        List<String> objClasses = new ArrayList<String>();
        for ( ObjectClass ocd : ocds )
        {
            objClasses.add( ocd.getNames().get( 0 ) );
        }
        String[] objectClasses = new String[objClasses.toArray().length];
        for ( int i = 0; i < objectClasses.length; i++ )
        {
            objectClasses[i] = objClasses.get( i );
        }
        codeGeneratorContext.setBindPassword( entry.getBrowserConnection().getConnection().getBindPassword() );
        codeGeneratorContext.setObjectClasses( objectClasses );
        List<EntryProperties> enList = new ArrayList<EntryProperties>();
        Collection<String> attributes = extractAttributes( ocds, schema );
        for ( String attrib : attributes )
        {
            AttributeType atd = schema.getAttributeTypeDescription( attrib );
            EntryProperties entryProperties = new EntryProperties();
            entryProperties.setLdapAttributeName( attrib );
            entryProperties.setJavaMemberName( attrib );
            boolean hasEntryAttribute = entry.getAttribute( attrib ) != null;
            entryProperties.setGen( hasEntryAttribute );
            entryProperties.setJavaType( getJavaType( atd, schema ) );
            entryProperties.setCardinality( getCardinality( atd, ocds, schema ) );
            enList.add( entryProperties );
        }
        codeGeneratorContext.setEntryProperties( enList );

        return codeGeneratorContext;
    }


    /**
     * Compute the Java type according to the syntax oid of the attribute
     * 
     * @param atd
     *            the attribute type description of the attribute of the
     *            selected entry
     * @param schema
     *            the schema of the entry
     * @return the Java type of the attribute
     */
    public static JavaType getJavaType( AttributeType atd, Schema schema )
    {
        String syntaxOid = SchemaUtils.getSyntaxNumericOidTransitive( atd, schema );
        boolean isBinary = SchemaUtils.isBinary( atd, schema );

        if ( SchemaConstants.INTEGER_SYNTAX.equals( syntaxOid ) )
        {
            return JavaType.INT;
        }
        else if ( SchemaConstants.BOOLEAN_SYNTAX.equals( syntaxOid ) )
        {
            return JavaType.BOOLEAN;
        }
        else if ( SchemaConstants.GENERALIZED_TIME_SYNTAX.equals( syntaxOid ) )
        {
            return JavaType.CALENDAR;
        }
        else if ( isBinary )
        {
            return JavaType.BYTE_ARRAY;
        }
        else
        {
            return JavaType.STRING;
        }
    }


    /**
     * Compute the cardinality of the given attribute
     * 
     * @param atd
     *            the attribute type description of the attribute of the
     *            selected entry
     * @param ocds
     *            the Object Class Descriptions of the entry
     * @param schema
     *            the schema of the entry
     * @return the cardinality of the attribute
     */
    public static Cardinality getCardinality( AttributeType atd, Collection<ObjectClass> ocds, Schema schema )
    {
        boolean isLowerBoundOne = SchemaUtils2.isMustAttribute( atd, ocds, schema );
        boolean isUpperBoundOne = atd.isSingleValued();

        if ( isLowerBoundOne && isUpperBoundOne )
        {
            return Cardinality.ONE;
        }
        else if ( isLowerBoundOne && !isUpperBoundOne )
        {
            return Cardinality.ONE_OR_MANY_SET;
        }
        else if ( !isLowerBoundOne && isUpperBoundOne )
        {
            return Cardinality.ZERO_OR_ONE;
        }
        else
        // if(!isLowerBoundOne && !isUpperBoundOne)
        {
            return Cardinality.ZERO_OR_MANY_SET;
        }
    }


    public static String firstToUpperCase( String string )
    {
        String post = string.substring( 1, string.length() );
        String first = ( "" + string.charAt( 0 ) ).toUpperCase();
        return first + post;
    }


    /**
     * Computes the default package name for an DN. The package name is composed
     * of all RDN values of the DN's parent. If the given DN or it's parent is
     * null or empty the empty string is returned.
     * 
     * @param dn
     *            the DN
     * @return the computed package name
     */
    public static String computePackageName( DN dn )
    {
        if ( dn == null || dn.isEmpty() )
        {
            return "";
        }

        // get the parent DN
        DN parentDn = DnUtils.getParent( dn );
        if ( parentDn == null || parentDn.isEmpty() )
        {
            return "";
        }

        // now concatenate all RDN values to the package name
        List<RDN> tempRdns = parentDn.getRdns();
        List<RDN> rdns = new ArrayList<RDN>( tempRdns );
        Collections.reverse( rdns );
        String packageName = "";
        for ( RDN rdn : rdns )
        {
            String rdnVal = rdn.getUpValue().toString();
            String validValue = JavaUtils.getValidValue( rdnVal );
            packageName += "." + validValue.toString().toLowerCase();
        }
        return packageName.substring( 1, packageName.length() );
    }


    /**
     * Computes the class name for the bean class to be generated from
     * structural object class of the given entry
     * 
     * @param strucObjClass
     *            structural object class of the given entry
     * @return the structural object class as the name of the bean class to be
     *         generated with first letter capitalized
     */
    public static String getClassName( ObjectClass strucObjClass )
    {
        String objClassName = strucObjClass.getNames().get( 0 );
        return firstToUpperCase( JavaUtils.getValidValue( objClassName ) );
    }


    /**
     * Extracts the attributes for an entry from its object class descriptions
     * and the schema
     * 
     * @param ocd
     *            Object class descriptions of the given entry
     * @param schema
     *            the schema of the entry
     * @return all the attributes of the entry
     */
    public static Collection<String> extractAttributes( Collection<ObjectClass> ocd, Schema schema )
    {
        List<String> attributes = new ArrayList<String>();
        attributes.addAll( SchemaUtils2.getAllAttributes( ocd, schema ) );
        attributes.remove( "objectClass" );
        return attributes;
    }

}
